package com.mt.roll.wms.dispatch.taskPool;



import com.mt.roll.core.dal.entity.LocationStorageSub;
import com.mt.roll.opcua.opcua.selfunion.Enum.PLCType;
import com.mt.roll.opcua.s7.my.s7connector.api.DaveArea;
import com.mt.roll.opcua.s7.my.s7connector.api.S7Connector;
import com.mt.roll.opcua.s7.my.s7connector.enmuc.PlcVarActual;
import com.mt.roll.opcua.s7.my.s7connector.enmuc.S7Client;
import com.mt.roll.opcua.s7.my.s7connector.service.S7Service;
import com.mt.roll.wms.dispatch.LocalDisPatchService;
import com.mt.roll.wms.dispatch.constant.QGSConstant;
import com.mt.roll.wms.dispatch.entity.FlagOfInOut;
import com.mt.roll.wms.dispatch.enums.machineHand1.MachineHand1Plc2WcsVarEnum;
import com.mt.roll.wms.dispatch.enums.machineHand1.MachineHand1Wcs2PlcVarEnum;
import com.mt.roll.wms.dispatch.taskPool.entity.RGVTask;
import com.mt.roll.wms.dispatch.taskPool.rgvTaskQueue.RGV1TaskIssueRunnable;
import com.mt.roll.wms.service.LocationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @Author: 蔡翔
 * @Date: 2019/10/30 16:23
 * @Version 1.0
 */
//todo 每个机械手 MachineHand1Runnable ，都要指定下S7Client.S7_1200, 这个是在枚举类里面配置的。
public class MachineHand1Runnable implements Runnable {
    private final static Logger logger = LoggerFactory.getLogger(MachineHand1Runnable.class);

    private LocalDisPatchService localDisPatchService;
    private LocationService locationService;
    private S7Client s7Client;


    public MachineHand1Runnable(LocalDisPatchService localDisPatchService, LocationService locationService){
        this.localDisPatchService = localDisPatchService;
        this.locationService = locationService;
        //S7Client.S7_1200 这个连接到时候就是 具体的机械手 plc了，一般来说一个机械手一个plc的。
        this.s7Client = S7Client.S7_1200;
    }

    public MachineHand1Runnable(){

    }
    //todo  1.把枚举类RGV1Plc2WcsVarEnum、RGV1Wcs2PlcVarEnum 录成实际位置(namespace,identifity,type) ;
    //      2.先事 写一个测试案例（controller）看看是否能够成功

    /**
     * type         1 上片（out口 生产好）（ 传输线=>箱子 ）；2 下片（in口 送去生产）（ 箱子=>传输线 ）
     * locationId   库位id(后面可能会被换成locationNum，因为对库位来说 wcs和plc中就是以locationNum对应上的)
     * position     in => 1,2,3,4 ;; out => 1,2,3,4
     * */
    private RGVTask getSubInfoFromPlc(FlagOfInOut flagOfInOut) throws Exception {
        RGVTask rgvTask = new RGVTask();
        //其实从byteFlag中就能 看出来哪些是in 哪些是out，从而就知道哪些是上片哪些是下片
        Byte subNum = null;
        Integer subStatus = null;
        Integer machineHandId = null;
        String[] timeArray = null;
        String[] subIdArray = null;
        Integer locationNum = null;
        Integer type = flagOfInOut.getType();
        Integer position = flagOfInOut.getPosition();
        if(type == QGSConstant.TYPE_OF_WORKING_PORT_OUT){
            //上片情况   out口
            if(position==1){
                //下面这些信息是从plc中读的。
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.OUT_1_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_1_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_1_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_1_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_1_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.OUT_1_ROT_ID.getLocationNum();
            }else if(position==2){
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.OUT_2_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_2_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_2_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_2_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_2_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.OUT_2_ROT_ID.getLocationNum();
            }else if(position==3){
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.OUT_3_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_3_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_3_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_3_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_3_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.OUT_3_ROT_ID.getLocationNum();
            }else{
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.OUT_4_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_4_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.OUT_4_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_4_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.OUT_4_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.OUT_4_ROT_ID.getLocationNum();
            }
        }else {
            //下片情况    in口
            if(position==1){
                //下面这些信息是从plc中读的。
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.IN_1_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_1_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_1_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_1_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_1_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.IN_1_ROT_ID.getLocationNum();
            }else if(position==2){
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.IN_2_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_2_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_2_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_2_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_2_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.IN_2_ROT_ID.getLocationNum();
            }else if(position==3){
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.IN_3_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_3_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_3_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_3_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_3_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.IN_3_ROT_ID.getLocationNum();
            }else{
                subNum = (Byte)getValue(MachineHand1Plc2WcsVarEnum.IN_4_PLATE_NUB);    //基板数量
                subStatus = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_4_STATE);
                machineHandId = (Integer)getValue(MachineHand1Plc2WcsVarEnum.IN_4_ROT_ID); //机械手id
                timeArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_4_Time_ARRAY);
                subIdArray = (String[])getValue(MachineHand1Plc2WcsVarEnum.IN_4_SUB_ID_ARRAY);
                locationNum = MachineHand1Plc2WcsVarEnum.IN_4_ROT_ID.getLocationNum();
            }
        }

        rgvTask.setType(type);
        rgvTask.setLocationNum(Long.valueOf(locationNum.toString()));
        List<LocationStorageSub> subList = new ArrayList<>();

        //构建基板
        //依据上面从plc中读出来的基板信息，，构建好基板list，准备消息入库做 wms业务上exchange操作。
        for (int i=0;i<subNum;i++){
            LocationStorageSub sub = new LocationStorageSub();
            sub.setSubStorageStatus(subStatus);
            sub.setSubCode(subIdArray[i]);
            sub.setOperatorTime(timeArray[i]);
            sub.setMachineHandId(machineHandId.toString());
            sub.setSubPosition(i+1);
            subList.add(sub);
        }
        rgvTask.setSubList(subList);
        return rgvTask;
    }

    private FlagOfInOut judgeTrue(Boolean in1,Boolean in2,Boolean in3,Boolean in4,Boolean out1,Boolean out2,Boolean out3,Boolean out4){
        if(in1==null || in2==null || in3==null || in4==null || out1==null || out2==null || out3==null || out4==null){
            return null;
        }
        FlagOfInOut flagOfInOut = new FlagOfInOut();
        Integer type = null;
        Integer position = null;
        if(in1 | in2 | in3 | in4){
            type = QGSConstant.TYPE_OF_WORKING_PORT_IN;
            if(in1){
                position = 1;
            }else if(in2){
                position = 2;
            }else if(in3){
                position = 3;
            }else{
                position = 4;
            }
        }else if(out1 | out2 | out3 | out4){
            type = QGSConstant.TYPE_OF_WORKING_PORT_OUT;
            if(out1){
                position = 1;
            }else if(out2){
                position = 2;
            }else if(out3){
                position = 3;
            }else{
                position = 4;
            }
        }
        flagOfInOut.setPosition(position);
        flagOfInOut.setType(type);
        return flagOfInOut;
    }


    /**
     * desc: 入库和出库 共用这个方法，目前阶段 taskType 都是1
     * 程度： 现在的调度程度就是 1.没有做报警相关处理；2.当检测到报警相关变量不等于预定值的时候 是直接中断此任务的 并且没有做相关处理。
     * return
     *          -1     下发任务失败
     *          -2     opc操作失败、异常（人工处理 检查opc server、opc client）
     *          2      发生报警（plc自检没有通过）（人工处理 检查具体报警问题然后解决）
     *          1      此次任务成功完成
     *
     * */
    @Override
    public void run() {
        logger.info("================= 执行的线程名："+Thread.currentThread().getName()+"；；  现在开始执行 机械手1 监听任务 :   ==================");
        //  实际环境   --开始
        while (true){
            while (true){
                try {

                    //1.轮询 机械手标志位后能得到信息：1.满箱还是空箱  2.上片还是下片 3.以及库位id（库位编号）
                    Boolean in1 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.IN_1_STOCKFULL);
                    Boolean in2 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.IN_2_STOCKFULL);
                    Boolean in3 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.IN_3_STOCKFULL);
                    Boolean in4 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.IN_4_STOCKFULL);
                    Boolean out1 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.OUT_1_STOCKEMPTY);
                    Boolean out2 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.OUT_2_STOCKEMPTY);
                    Boolean out3 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.OUT_3_STOCKEMPTY);
                    Boolean out4 = (Boolean)getValue(MachineHand1Plc2WcsVarEnum.OUT_4_STOCKEMPTY);
                    //这里写轮询代码
                    if(in1 | in2 | in3 | in4 | out1 | out2 | out3 | out4){
                        //从plc 中获取到基板信息，并且构建好 task
                        RGVTask rgvTask = getSubInfoFromPlc(judgeTrue(in1, in2, in3, in4, out1, out2, out3, out4));
                        //  todo 从plc中取完数据，再重置里面的信息。
                        //2. 把构建好的任务提交给线程池
                        RGV1TaskIssueRunnable runnable = new RGV1TaskIssueRunnable(localDisPatchService,rgvTask,locationService);
                        //因为rgv1SinglePool 是单线程池的，就算一次性被很多机械手的runnable投递也没关系，这些投递的任务会在线程池外面排好队。
                        Future submit = QGSThreadPool.rgv1SinglePool.submit(runnable);

                        Object taskResult = new Object();
                        try {
                            //线程阻塞住，等待RGV任务完成，然后重置机械手操作。
                            //注意这里必须要让线程阻塞住，因为
                            taskResult = submit.get();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        }
                    }

                    //轮询标志位间隔，现在1s
                    Thread.sleep(1000);
                }
                catch (Exception e){
                    logger.info("opc 操作异常，请稍后重试,异常信息："+e.getMessage());

                    break;
                }
            }
        }
        //  实际环境   --结束
    }


    //PlcVarActual 到时候改成你们的 xxxPlcToWcs 或者 xxxWcsToPlc
    private Object read(S7Client s7Client,MachineHand1Plc2WcsVarEnum var) throws UnsupportedEncodingException, ParseException {
        return s7Client.read(var.getArea(), var.getAreaNumber(), var.getByteOffset(), var.getBitOffset(), var.getLength(), var.getStrSize(), var.getType());
    }
    private void write(S7Client s7Client,MachineHand1Plc2WcsVarEnum var,Object newValue) {
        s7Client.write(var.getArea(), var.getAreaNumber(), var.getByteOffset(), var.getBitOffset(), var.getStrSize(), var.getType(),newValue);
    }

    /**
     * desc: 检测是否发生报警
     * return：
     *      true  发生了报警
     *      false 没有发生报警
     *      todo 机械手的报警后续补充
     * */
//    private Boolean alarmDetect() throws Exception {
//        //todo alarm 其实需要实时检测，这样的话就不能写在调度程序里面了。只能写在第三方检测 线程里面。
//        // 但是这里alarm 是分两种的：1.一种是不影响任务  2.影响任务（任务就进行不下去了 需要结束任务，，这种情况就不适用第三方检测程序了。）
//
//        return Boolean.valueOf(getValue(MachineHand1Plc2WcsVarEnum.Alarm));
//    }

    /**
     * desc: PLC自检  是否可执行 或者报警 (是一个 “或” 的操作 一旦有一个条件满足都会跳出循环)
     * return：
     *      1  可执行
     *      2  发生报警
     *      -2 操作opc失败
     * status：not work（待定，这个暂时用不到）
     * */
    private Integer plcJudgeIsOk() {
        Integer res = 0;
        try {
            while (true){
                //Boolean aBoolean = alarmDetect();
//                Integer value = Integer.valueOf(getValue(MachineHand1Plc2WcsVarEnum.L_1_IN_1_STOCKFULL));
////                if(aBoolean){
////                    res = 2;
////                    break;
////                }
//                if(value == 1){
//                    res = 1;
//                    break;
//                }

                Thread.sleep(500);
            }

        }catch (Exception e){
            logger.info("plcJudgeIsOk 方法中出现读取opc 异常");
            res = -2;
        }
        return res;

    }

    /**
     * desc: 判断当前堆垛机是否满足下发任务的条件
     * return：
     *      true  满足
     *      false 不满足
     * */
//    private Boolean judgeDDJStatusIsOk() throws Exception {
//        //target == true
//        Boolean rgvStatus = Boolean.valueOf(getValue(MachineHand1Plc2WcsVarEnum.RGV1));
//        //target == true
//        Boolean free = Boolean.valueOf(getValue(MachineHand1Plc2WcsVarEnum.Free));
//        //target == true
//        Boolean online = Boolean.valueOf(getValue(MachineHand1Plc2WcsVarEnum.Online));
//        //target == false
//        Boolean alarm = Boolean.valueOf(getValue(MachineHand1Plc2WcsVarEnum.Alarm));
//        //target == 0
//        Integer stb = Integer.valueOf(getValue(MachineHand1Wcs2PlcVarEnum.STB));
//        //target == 0
//        Integer ack = Integer.valueOf(getValue(MachineHand1Plc2WcsVarEnum.ACK));
//
//        logger.info("判断是否满足下发条件： rgvStatus:"+rgvStatus+"  free:"+free+"  online:"+online+"  alarm:"+alarm+"  stb:"+stb+"  ack:"+ack);
//        if(online && rgvStatus && free && !alarm && stb == 0 && ack == 0){
//            logger.info("堆垛机 满足 下发任务条件");
//            return true;
//        }else {
//            logger.info("堆垛机 不满足 下发任务条件");
//            return false;
//        }
//    }

    private Object getValue(MachineHand1Plc2WcsVarEnum var) throws Exception {
        return this.s7Client.read(
                DaveArea.DB,
                var.getAreaNumber(),
                var.getByteOffset(),
                var.getBitOffset(),
                var.getLength(),
                var.getStrSize(),
                var.getType()
        );
    }
    private void setValue(MachineHand1Wcs2PlcVarEnum var, Object newValue) throws Exception {
        this.s7Client.write(
                DaveArea.DB,
                var.getAreaNumber(),
                var.getByteOffset(),
                var.getBitOffset(),
                var.getStrSize(),
                var.getType(),
                newValue

        );
    }

    private Integer waitingForKeepTarget(MachineHand1Plc2WcsVarEnum var, Integer targetValue, Long deadLine) {
        Integer res = 0;
        long start = System.currentTimeMillis();
        while (true){
            try {
                long end = System.currentTimeMillis();

                //先判断一下是否发生告警
//                if(alarmDetect()){
//                    res = -2;
//                    break;
//                }
                Integer now = (Integer) getValue(var);

                if(targetValue.equals(now) && ((end-start)>2000)){
                    res = 1;
                    break;
                }

                if((end-start)>deadLine){
                    res = -1;
                    break;
                }
                Thread.sleep(200);
            }catch (Exception e){
                logger.info("opc 访问异常。");
                res = 2;
                break;
            }
        }
        return res;
    }
    //-1 超时退出; -2 发生告警； 2 opc 访问异常；1 成功
    private Integer waitingForTarget(MachineHand1Plc2WcsVarEnum var, Integer targetValue, Long deadLine) {
        Integer res = 0;
        long start = System.currentTimeMillis();
        while (true){
            try {
                long end = System.currentTimeMillis();
                if((end-start)>deadLine){
                    res = -1;
                    break;
                }
                //先判断一下是否发生告警
//                if(alarmDetect()){
//                    res = -2;
//                    break;
//                }

                Integer now = (Integer) getValue(var);
                if(targetValue.equals(now)){
                    res = 1;
                    break;
                }
                Thread.sleep(200);
            }catch (Exception e){
                logger.info("opc 访问异常。");
                res = 2;
                break;
            }
        }
        return res;
    }
    private Integer waitingForTarget(MachineHand1Plc2WcsVarEnum var, Boolean targetValue, Long deadLine) {
        Integer res = 0;
        long start = System.currentTimeMillis();
        while (true){
            try {
                long end = System.currentTimeMillis();
                if((end-start)>deadLine){
                    res = -1;
                    break;
                }

                Boolean now = (Boolean) getValue(var);

                if(targetValue.equals(now)){
                    res = 1;
                    break;
                }
                Thread.sleep(200);
            }catch (Exception e){
                logger.info("opc 访问异常。");
                res = 2;
                break;
            }
        }
        return res;
    }
}
